package top.honlnk.czuan.common.utils;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import top.honlnk.czuan.common.config.ConstantConfig;
import top.honlnk.czuan.common.exception.CustomException;

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class SnowflakeIdUtil {

    // 引入Redis模板，用于持久化存储最后的时间戳
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ConstantConfig constantConfig;

    // 定义时间戳部分位数为28位
    private static final int TIMESTAMP_BITS = 28; // 没用到，但还是选择了保留
    // 定义序列号部分位数为3位
    private static final int SEQUENCE_BITS = 3;

    // 计算最大序列号（即2^SEQUENCE_BITS - 1）
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);


    // 使用AtomicInteger来确保线程安全地生成递增的序列号
    private static final AtomicInteger sequence = new AtomicInteger(0);
    // volatile关键字保证变量对所有线程可见
    private static volatile long lastTimestamp = -1L;

    // Redis中保存最后时间戳的键名
    private static final String LAST_TIMESTAMP_KEY = "snowflake:last_timestamp";

    // 延迟初始化EPOCH（避免构造函数中直接使用未注入的constantConfig）
    private long EPOCH;



    /**
     * 初始化方法，在Bean创建后自动调用。
     * 在依赖注入完成后初始化EPOCH
     * 尝试从Redis中读取最后的时间戳，如果存在则更新lastTimestamp。
     */
    @PostConstruct
    private void init() {
        this.EPOCH = DateUtil.parseDate(constantConfig.getSnowflakeIdStartDate()).getTime() / 1000L;

        String lastTsStr = (String) redisTemplate.opsForValue().get(LAST_TIMESTAMP_KEY);
        if (lastTsStr != null) {
            lastTimestamp = Long.parseLong(lastTsStr);
        }
    }

    /**
     * 获取下一个ID的方法。
     * 如果当前秒与上次相同，则增加序列号；否则重置序列号并更新时间戳。
     * 每次成功获取新ID时，都会将最新的时间戳存入Redis以应对服务重启。
     *
     * @return 生成的唯一ID，类型为Integer
     */
    public synchronized Integer nextId() {
        long currentTimestamp = currentTimeSeconds();

        // 如果当前时间小于上一次的时间戳，说明系统时间被调整到了过去，拒绝生成ID
        if (currentTimestamp < lastTimestamp) {
            log.error("时钟向后移动。拒绝生成id。");
            throw new CustomException("系统繁忙拒绝了本次操作，请重新您的上一步操作");
        }

        // 如果是同一秒内请求，则尝试增加序列号
        if (currentTimestamp == lastTimestamp) {
            int seq = sequence.incrementAndGet();
            // 如果序列号超出最大值，则等待下一秒
            if (seq > MAX_SEQUENCE) {
                sequence.set(0);
                currentTimestamp = tilNextSecond(lastTimestamp);
            }
        } else {
            // 如果不是同一秒，则重置序列号
            sequence.set(0);
        }

        // 更新最新时间戳，并将其保存到Redis中
        lastTimestamp = currentTimestamp;
        redisTemplate.opsForValue().set(LAST_TIMESTAMP_KEY, String.valueOf(currentTimestamp));

        // 根据SEQUENCE_BITS来确定左移位数
        // 生成最终的ID：(当前时间戳 - 起始时间) 左移 序列号位数 | 当前序列号
        return (int)(((currentTimestamp - EPOCH) << SEQUENCE_BITS) | sequence.get());
    }

    /**
     * 等待直到进入下一秒。
     *
     * @param lastTimestamp 上一次的时间戳（秒级）
     * @return 下一秒的时间戳（秒级）
     */
    private long tilNextSecond(long lastTimestamp) {
        long timestamp = currentTimeSeconds();
        while (timestamp <= lastTimestamp) {
            timestamp = currentTimeSeconds();
        }
        return timestamp;
    }

    /**
     * 获取当前时间戳（以秒为单位）。
     *
     * @return 当前时间戳（秒级）
     */
    private long currentTimeSeconds() {
        return System.currentTimeMillis() / 1000L;
    }
}